package editor.code;

import editor.AutoCompleteList;
import static editor.Configuration.AUTOCOMPLETE;
import static editor.Configuration.TOGGLE_LINE_COMMENT;
import editor.ErrorHandler;
import editor.PwnIDE;
import event.TextReplacementEvent;
import event.UndoSet;
import event.Undoable;
import lang.asm.ASM;
import lang.asm.ASMLoad;
import lang.asm.DataTypeUtil;
import model.caret.TextCaret;
import model.code.Function;
import model.code.GlobalVariable;
import model.code.Structure;
import model.code.line.*;
import model.symbol.*;
import observer.ObservedBoolean;
import observer.ObservedInt;
import util.IntList;
import util.StringUtil;

import java.awt.*;
import java.awt.event.KeyEvent;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;

/**
 * This class is used for editing assembly language functions.
 *
 * FEATURE: Links in comments
 * FEATURE: Find, Forward, Backward, Rename
 * FEATURE: Bug fixing
 *
 * @author Neil Dickson
 */
public class ASMCodePanel extends CodePanel {
	public static Color			COMMENT_COLOUR			= Color.BLUE;
	public static Color			COMMENT_BG_COLOUR		= new Color(0xC0C0FF);
	public static Font			COMMENT_FONT			= BOLD_FONT;

	public static Color			MNEMONIC_COLOUR			= new Color(0x0080FF);
	public static Font			MNEMONIC_FONT			= BOLD_FONT;
	public static Color			JUMP_COLOUR				= new Color(0x000080);

	public static Color			LABEL_COLOUR			= Color.BLACK;
	public static Font			LABEL_FONT				= BOLD_FONT;

	public static Color			REGISTER_COLOUR			= new Color(0x00C000);
	public static Font			REGISTER_FONT			= BOLD_FONT;

	public static Color			CONSTANT_COLOUR			= new Color(0x8000FF);
	public static Font			CONSTANT_FONT			= CODE_FONT;

	public static Color			GLOBAL_VAR_COLOUR		= new Color(0x00FF00);
	public static Font			GLOBAL_VAR_FONT			= CODE_FONT;

	public static Color			LOCAL_VAR_COLOUR		= new Color(0x008000);
	public static Font			LOCAL_VAR_FONT			= CODE_FONT;

	public static Color			FUNCTION_COLOUR			= new Color(0x0000FF);
	public static Font			FUNCTION_FONT			= CODE_FONT;

	public static Color			STRUCTURE_COLOUR		= new Color(0x0000FF);
	public static Font			STRUCTURE_FONT			= CODE_FONT;

	public static Color			OPERATOR_COLOUR			= Color.DARK_GRAY;
	public static Font			OPERATOR_FONT			= CODE_FONT;

	public static Color			IMMEDIATE_COLOUR		= new Color(0xFF8000);
	public static Font			IMMEDIATE_FONT			= CODE_FONT;

	public static Color			STRING_COLOUR			= new Color(0xFF8000);
	public static Font			STRING_FONT				= CODE_FONT;

	public static Color			KEYWORD_COLOUR			= IMMEDIATE_COLOUR;
	public static Font			KEYWORD_FONT			= BOLD_FONT;

	public static Color			SEGMENT_COLOUR			= OPERATOR_COLOUR;
	public static Font			SEGMENT_FONT			= BOLD_FONT;

	public ASMCodePanel(Function function) {
		super(function);
	}

	public void keyPressed(KeyEvent e) {
		try {
			int newUndoCombining = UNDO_NONE;
			boolean killAutocomplete = (autocompleteList!=null && e.getKeyCode()!=KeyEvent.VK_CONTROL && e.getKeyCode()!=KeyEvent.VK_SHIFT && e.getKeyCode()!=KeyEvent.VK_ALT);
			if (caretRef.isTextCaret(lines)) {
				TextCaret caret = (TextCaret)caretRef.get();
				Undoable event = null;
				// Toggle Line Comment
				if (TOGGLE_LINE_COMMENT.isTriggered(e)) {
					if (caret.isEmpty()) {
						ASMLine line = (ASMLine)lines.get(caret.getLine());
						boolean isAlreadyComment = line.getCommentStart()==0;
						int x = LineRenderer.getX(line,caret.getCol());
						int newLine = Math.min(caret.getLine()+1,lines.size()-1);
						if (!isAlreadyComment) {
							event = lines.insert(caret.getLine(),0,";");
						}
						else {
							event = lines.replace(caret.getLine(),0,caret.getLine(),1,"");
						}
						int newColumn = LineRenderer.getColumn(lines.get(newLine),x);
						x = LineRenderer.getX(lines.get(newLine),newColumn);
						caret.set(newLine,newColumn,x);
					}
					else {
						int firstLine = caret.getFirstLine();
						int lastLine = caret.getLastLine();
						int firstLineCol = caret.getFirstLineCol();
						int lastLineCol = caret.getLastLineCol();
						// Don't consider the last line if none of it is actually selected
						int lastLineConsidered = (lastLineCol>0 || firstLine==lastLine) ? lastLine : lastLine-1;
						// First, determine whether the lines are all comments or all not comments or a mix
						boolean areAllComments = true;
						boolean areAllNotComments = true;
						for (int lineNum=firstLine;lineNum<=lastLineConsidered;++lineNum) {
							ASMLine line = (ASMLine)lines.get(lineNum);
							boolean isAlreadyComment = line.getCommentStart()==0;
							if (isAlreadyComment) {
								areAllNotComments = false;
							}
							else {
								areAllComments = false;
							}
						}

						ArrayList<TextReplacementEvent> events = new ArrayList<TextReplacementEvent>(lastLine-firstLine+1);
						// Then do the comment toggle/add
						for (int lineNum=firstLine;lineNum<=lastLineConsidered;++lineNum) {
							// Toggle if all one or the other
							if (areAllComments || areAllNotComments) {
								if (areAllComments) {
									events.add(lines.replace(lineNum,0,lineNum,1,""));
								}
								else {
									events.add(lines.insert(lineNum,0,";"));
								}
							}
							// Add if mixed
							else {
								events.add(lines.insert(lineNum,0,";"));
							}
						}

						if (events.size()==1) {
							event = events.get(0);
						}
						else {
							event = new UndoSet(events.toArray(new Undoable[events.size()]),"Toggle Line Comment in "+function.getName());
						}

						if (areAllComments) {
							firstLineCol = Math.max(0,firstLineCol-1);
							lastLineCol = Math.max(0,lastLineCol-1);
						}
						else {
							if (firstLineCol>0) {
								++firstLineCol;
							}
							if (lastLineCol>0) {
								++lastLineCol;
							}
						}
						Line first = lines.get(firstLine);
						Line last = lines.get(lastLine);
						if (firstLine==caret.getHeadLine()) {
							caret.set(firstLine,firstLineCol,lastLine,lastLineCol,LineRenderer.getX(first,firstLineCol));
						}
						else {
							caret.set(lastLine,lastLineCol,firstLine,firstLineCol,LineRenderer.getX(last,lastLineCol));
						}
					}
				}
				else if (AUTOCOMPLETE.isTriggered(e)) {
					if (autocompleteList==null) {
						int lineNum = caret.getHeadLine();
						ASMLine line = (ASMLine)lines.get(lineNum);
						Object[] refs = line.getReferences();
						int column = caret.getHeadCol();
						int prevToken = -1;
						int prevPrevToken = -1;
						int curToken = -1;
						int curTokenLength = -1;
						int currentMode = function.getMode();
						String alreadyTyped = "";
						boolean isInsideSquareBrackets = false;
						if (column > 0) {
							for (int i=0;i<line.getNumReferences();++i) {
								if (line.isOperator(i,'[')) {
									isInsideSquareBrackets = true;
								}
								else if (line.isOperator(i,']')) {
									isInsideSquareBrackets = false;
								}
								if (column<=line.getTokenColumn(i+1)) {
									if (refs[i]==Line.WHITE_SPACE_MARKER) {
										curToken = -1;
										curTokenLength = -1;
										alreadyTyped = "";
									}
									else if (refs[i]==Line.OPERATOR_MARKER) {
										prevPrevToken = prevToken;
										prevToken = i;
										curToken = -1;
										curTokenLength = -1;
										alreadyTyped = "";
									}
									else {
										curToken = i;
										alreadyTyped = line.toString(line.getTokenColumn(i),column);
										curTokenLength = line.getTokenColumn(i+1)-line.getTokenColumn(i);
									}
									break;
								}
								if (refs[i]!=Line.WHITE_SPACE_MARKER) {
									prevPrevToken = prevToken;
									prevToken = i;
								}
							}
						}

						Scope languageScope = ASM.language.getScope();
						Scope scope = function.getScope();
						ArrayList<SimpleLine> options = new ArrayList<SimpleLine>();
						// Special handling for "assume" options
						if (line.isAssumeLine()) {
							if ((prevToken>=0 && (refs[prevToken]==ASM.ASSUME || line.isOperator(prevToken,','))) || (curToken>=0 && refs[curToken]==ASM.ASSUME && alreadyTyped.length()==curTokenLength)) {
								if (curToken>=0 && refs[curToken]==ASM.ASSUME) {
									event = replaceSelection(" ");
									++column;
									alreadyTyped = "";
								}
								HashMap<Register,DataType> assumptions = line.getAssumptions();
								for (Register register : ASM.language.registers) {
									if (DataTypeUtil.isAddressReg(register,currentMode)) {
										ArrayList<Token> tempTokens = new ArrayList<Token>(4);
										tempTokens.add(new Token(register.toString(),register));
										tempTokens.add(new Token(":",Line.OPERATOR_MARKER));
										tempTokens.add(new Token("ptr",ASM.PTR));
										tempTokens.add(new Token(" ",Line.WHITE_SPACE_MARKER));
										options.add(new SimpleLine(tempTokens));
										if (assumptions.containsKey(register)) {
											tempTokens = new ArrayList<Token>(3);
											tempTokens.add(new Token(register.toString(),register));
											tempTokens.add(new Token(":",Line.OPERATOR_MARKER));
											tempTokens.add(new Token("nothing",ASM.PTR));
											options.add(new SimpleLine(tempTokens));
										}
									}
								}
							}
							else if ((prevToken>=0 && refs[prevToken]==ASM.PTR) || (curToken>=0 && refs[curToken]==ASM.PTR && alreadyTyped.length()==curTokenLength)) {
								if (curToken>=0 && refs[curToken]==ASM.PTR) {
									event = replaceSelection(" ");
									++column;
									alreadyTyped = "";
								}
								for (Keyword type : ASMLoad.instance.dataDeclarationKeywords.values()) {
									// Exclude the keywords like "db"
									if (type.getName().length()>2) {
										ArrayList<Token> tempTokens = new ArrayList<Token>(1);
										tempTokens.add(new Token(type.toString(),type));
										options.add(new SimpleLine(tempTokens));
									}
								}
								for (Symbol symbol : scope.getAll()) {
									if (symbol instanceof Structure) {
										ArrayList<Token> tempTokens = new ArrayList<Token>(1);
										tempTokens.add(new Token(symbol.toString(),symbol));
										options.add(new SimpleLine(tempTokens));
									}
								}
							}
						}
						// Following "offset"
						else if ((prevToken>=0 && refs[prevToken]==Directive.OFFSET) || (curToken>=0 && refs[curToken]==Directive.OFFSET && alreadyTyped.length()==curTokenLength)) {
							if (curToken>=0 && refs[curToken]==Directive.OFFSET) {
								event = replaceSelection(" ");
								++column;
								alreadyTyped = "";
							}
							for (Symbol symbol : scope.getAll()) {
								if (symbol instanceof Function || symbol instanceof GlobalVariable || symbol instanceof LineLabel) {
									ArrayList<Token> tempTokens = new ArrayList<Token>(1);
									tempTokens.add(new Token(symbol.getName().toString(),symbol));
									options.add(new SimpleLine(tempTokens));
								}
							}
						}
						// Following "sizeof"
						else if ((prevToken>=0 && refs[prevToken]==Directive.SIZEOF) || (curToken>=0 && refs[curToken]==Directive.SIZEOF && alreadyTyped.length()==curTokenLength)) {
							if (curToken>=0 && refs[curToken]==Directive.SIZEOF) {
								event = replaceSelection(" ");
								++column;
								alreadyTyped = "";
							}
							for (Keyword type : ASMLoad.instance.dataDeclarationKeywords.values()) {
								// Exclude the keywords like "db"
								if (type.getName().length()>2) {
									ArrayList<Token> tempTokens = new ArrayList<Token>(1);
									tempTokens.add(new Token(type.getName().toString(),type));
									options.add(new SimpleLine(tempTokens));
								}
							}
							for (Symbol symbol : scope.getAll()) {
								if (symbol instanceof Structure || symbol instanceof GlobalVariable || symbol instanceof LocalVariable) {
									ArrayList<Token> tempTokens = new ArrayList<Token>(1);
									tempTokens.add(new Token(symbol.getName().toString(),symbol));
									options.add(new SimpleLine(tempTokens));
								}
							}
						}
						// Following +,-,*,/,( operator or directive
						else if ((prevToken>=0 && (refs[prevToken] instanceof Directive || line.isOperator(prevToken,'+') || line.isOperator(prevToken,'-') || line.isOperator(prevToken,'*') || line.isOperator(prevToken,'/') || line.isOperator(prevToken,'(')))
								|| (curToken>=0 && refs[curToken] instanceof Directive && alreadyTyped.length()==curTokenLength)) {
							if (curToken>=0 && refs[curToken] instanceof Directive && alreadyTyped.length()==curTokenLength) {
								event = replaceSelection(" ");
								++column;
								alreadyTyped = "";
							}
							for (Symbol symbol : languageScope.getAll()) {
								if (symbol instanceof Directive.UnaryDirective) {
									ArrayList<Token> tempTokens = new ArrayList<Token>(1);
									tempTokens.add(new Token(symbol.getName().toString(),symbol));
									options.add(new SimpleLine(tempTokens));
								}
							}
							for (Symbol symbol : scope.getAll()) {
								if (symbol instanceof Constant) {
									ArrayList<Token> tempTokens = new ArrayList<Token>(1);
									tempTokens.add(new Token(symbol.getName().toString(),symbol));
									options.add(new SimpleLine(tempTokens));
								}
							}
						}
						// Following a Constant, number, or ) operator
						else if ((prevToken>=0 && (refs[prevToken] instanceof Constant || refs[prevToken] instanceof Number || line.isOperator(prevToken,')'))) || (curToken>=0 && (refs[curToken] instanceof Constant || refs[curToken] instanceof Number) && alreadyTyped.length()==curTokenLength)) {
							if (curToken>=0 && (refs[curToken] instanceof Constant || refs[curToken] instanceof Number) && alreadyTyped.length()==curTokenLength) {
								event = replaceSelection(" ");
								++column;
								alreadyTyped = "";
							}
							for (Symbol symbol : languageScope.getAll()) {
								if (symbol instanceof Directive.BinaryDirective) {
									ArrayList<Token> tempTokens = new ArrayList<Token>(1);
									tempTokens.add(new Token(symbol.getName().toString(),symbol));
									options.add(new SimpleLine(tempTokens));
								}
							}
						}
						// First word of a line or first word after a line label
						else if (prevToken<0 || line.isOperator(prevToken,':')) {
							for (Symbol symbol : languageScope.getAll()) {
								if (symbol instanceof Mnemonic || (symbol instanceof Keyword && ((Keyword)symbol).isLineStart())) {
									ArrayList<Token> tempTokens = new ArrayList<Token>(1);
									tempTokens.add(new Token(symbol.getName().toString(),symbol));
									options.add(new SimpleLine(tempTokens));
								}
							}
	//						if (alreadyTyped!=null && alreadyTyped.length()>0) {
	//							if (((alreadyTyped.charAt(0)|0x20)>='a' && (alreadyTyped.charAt(0)|0x20)<='z') || alreadyTyped.charAt(0)=='_') {
	//								if (!languageScope.contains(alreadyTyped) && !scope.contains(alreadyTyped)) {
	//									ArrayList<Token> tempTokens = new ArrayList<Token>(3);
	//									tempTokens.add(new Token(alreadyTyped,new LineLabel(alreadyTyped,"")));
	//									tempTokens.add(new Token(":",Token.OPERATOR_MARKER));
	//									tempTokens.add(new Token("\n",Token.WHITE_SPACE_MARKER));
	//									options.add(new SimpleLine(tempTokens));
	//								}
	//								if (!languageScope.contains(alreadyTyped+"Loop") && !scope.contains(alreadyTyped+"Loop")) {
	//									ArrayList<Token> tempTokens = new ArrayList<Token>(3);
	//									tempTokens.add(new Token(alreadyTyped+"Loop",new LineLabel(alreadyTyped+"Loop","")));
	//									tempTokens.add(new Token(":",Token.OPERATOR_MARKER));
	//									tempTokens.add(new Token("\n",Token.WHITE_SPACE_MARKER));
	//									options.add(new SimpleLine(tempTokens));
	//								}
	//							}
	//						}
							for (Symbol symbol : scope.getAll()) {
								if (symbol instanceof Function && ((Function)symbol).getCallingConvention().equals(Function.INLINE_TEXT)) {
									ArrayList<Token> tempTokens = new ArrayList<Token>(1);
									tempTokens.add(new Token(symbol.getName().toString(),symbol));
									options.add(new SimpleLine(tempTokens));
								}
							}
						}
						// Beginning of an operand
						else if (line.isOperator(prevToken,',') || (refs[prevToken]!=null && (refs[prevToken] instanceof Mnemonic || refs[prevToken]==ASM.INVOKE))) {
							int primaryKeyword = line.getPrimaryKeyword();
							if (primaryKeyword>=0 && (refs[primaryKeyword] instanceof Mnemonic || refs[primaryKeyword]==ASM.INVOKE)) {
								ArrayList<Object> operands = line.getOperands();
								int[] opTypes = ASMLine.getOperandTypes(operands,currentMode);
								int operandNumber = 0;
								if (line.isOperator(prevToken,',')) {
									operandNumber = line.getOperandIndices().indexOf(prevToken+1);
								}
								ArrayList<Object> allowedTypes = new ArrayList<Object>();
								if (refs[primaryKeyword] instanceof Mnemonic) {
									ArrayList<Object[]> allowedOpTypes = ((Mnemonic)refs[primaryKeyword]).getOperandTypes(currentMode);
									for (Object[] types : allowedOpTypes) {
										boolean isCompatible = types.length>operandNumber;
										for (int i=0;i<operandNumber && isCompatible;++i) {
											if (types[i] instanceof Integer) {
												isCompatible = DataTypeUtil.isCompatibleType(opTypes[i],(Integer)types[i]);
											}
											else {	// types[i] instanceof String
												isCompatible = operands.get(i).toString().equalsIgnoreCase((String)types[i]);
											}
										}
										if (isCompatible) {
											allowedTypes.add(types[operandNumber]);
										}
									}
								}

								boolean registerAllowed = false;
								boolean immediateAllowed = false;
								boolean memoryAllowed = false;
								boolean functionAllowed = false;
								boolean labelAllowed = false;
								boolean parameterAllowed = false;
								ArrayList<String> specialAllowed = new ArrayList<String>();
								if (refs[primaryKeyword]==ASM.INVOKE) {
									if (operandNumber!=0) {
										registerAllowed = true;
										immediateAllowed = true;
										memoryAllowed = true;
										allowedTypes.clear();
										parameterAllowed = true;
									}
									else {
										functionAllowed = true;
									}
								}
								else {
									for (Object type : allowedTypes) {
										if (type instanceof Integer) {
											int intType = (Integer)type;
											if (!registerAllowed && DataTypeUtil.containsRegisterType(intType)) {
												registerAllowed = true;
												if (!memoryAllowed && DataTypeUtil.containsMemoryType(intType)) {
													memoryAllowed = true;
												}
											}
											else if (!memoryAllowed && DataTypeUtil.containsMemoryType(intType)) {
												memoryAllowed = true;
											}
											else if (!immediateAllowed && DataTypeUtil.isImmediateType(intType)) {
												immediateAllowed = true;
											}
											else if (!functionAllowed && DataTypeUtil.isFunctionType(intType)) {
												functionAllowed = true;
											}
											else if (!labelAllowed && DataTypeUtil.isLabelType(intType)) {
												labelAllowed = true;
											}
											// Ignore unknown type
										}
										else {	// type instanceof String
											specialAllowed.add((String)type);
										}
									}
								}

								if (registerAllowed || !specialAllowed.isEmpty()) {
									for (Symbol symbol : languageScope.getAll()) {
										if (registerAllowed && symbol instanceof Register && (parameterAllowed || DataTypeUtil.isCompatibleType(((Register)symbol).getType(),allowedTypes))) {
											ArrayList<Token> tempTokens = new ArrayList<Token>(1);
											tempTokens.add(new Token(symbol.getName().toString(),symbol));
											options.add(new SimpleLine(tempTokens));
										}
										else if (specialAllowed.contains(symbol.toString())) {
											ArrayList<Token> tempTokens = new ArrayList<Token>(1);
											tempTokens.add(new Token(symbol.getName().toString(),symbol));
											options.add(new SimpleLine(tempTokens));
										}
									}
								}
								if (immediateAllowed) {
									for (Keyword symbol : ASMLoad.instance.allKeywords.values()) {
										if (symbol instanceof Directive.UnaryDirective) {
											ArrayList<Token> tempTokens = new ArrayList<Token>(1);
											tempTokens.add(new Token(symbol.getName().toString(),symbol));
											options.add(new SimpleLine(tempTokens));
										}
									}
								}
								if (memoryAllowed) {
									// Options like "byte ptr " and "dword ptr "
									IntList sizes = new IntList();
									for (Object type : allowedTypes) {
										if (type instanceof Integer && DataTypeUtil.containsMemoryType((Integer)type)) {
											final int size = DataTypeUtil.TYPE_MAP[(Integer)type].size;
											if (size!=DataTypeUtil.TypeMapping.UNKNOWN_SIZE && !sizes.contains(size/8)) {
												sizes.add(size/8);
											}
										}
									}
									for (Keyword symbol : ASMLoad.instance.dataDeclarationKeywords.values()) {
										// NOTE: Checking for more than 2 characters excludes the keywords like "db"
										if (symbol instanceof KeywordDataType && symbol.getName().length()>2 && sizes.contains(((KeywordDataType)symbol).getSize())) {
											ArrayList<Token> tempTokens = new ArrayList<Token>(4);
											tempTokens.add(new Token(symbol.getName().toString(),symbol));
											tempTokens.add(new Token(" ",Line.WHITE_SPACE_MARKER));
											tempTokens.add(new Token("ptr",ASM.PTR));
											tempTokens.add(new Token(" ",Line.WHITE_SPACE_MARKER));
											options.add(new SimpleLine(tempTokens));
										}
									}

									// Options like "[eax]", or "[eax]." if assumption exists on eax
									for (Register register : ASM.language.registers) {
										if (DataTypeUtil.isAddressReg(register,currentMode)) {
											ArrayList<Token> tempTokens = new ArrayList<Token>(4);
											tempTokens.add(new Token("[",Line.OPERATOR_MARKER));
											tempTokens.add(new Token(register.getName().toString(),register));
											tempTokens.add(new Token("]",Line.OPERATOR_MARKER));
											DataType type = line.getAssumptions().get(register);
											if (type!=null && type instanceof Structure) {
												tempTokens.add(new Token(".",Line.OPERATOR_MARKER));
											}
											options.add(new SimpleLine(tempTokens));
										}
									}
								}
								if (immediateAllowed || memoryAllowed || functionAllowed || labelAllowed) {
									for (Symbol symbol : scope.getAll()) {
										if (symbol instanceof Constant && (immediateAllowed || functionAllowed)) {
											Constant constant = (Constant)symbol;
											if (immediateAllowed && !constant.isFunctionConstant()) {
												boolean allowed = parameterAllowed;
												if (!allowed) {
													Immediate immediate = constant.getImmediate();
													if (immediate!=null) {
														int type = DataTypeUtil.getOperandType(immediate,currentMode);
														allowed = DataTypeUtil.isCompatibleType(type,allowedTypes);
													}
													else {
														allowed = false;
													}
												}
												if (allowed) {
													ArrayList<Token> tempTokens = new ArrayList<Token>(1);
													tempTokens.add(new Token(symbol.getName().toString(),symbol));
													options.add(new SimpleLine(tempTokens));
												}
											}
											else if (functionAllowed && constant.isFunctionConstant()) {
												ArrayList<Token> tempTokens = new ArrayList<Token>(1);
												tempTokens.add(new Token(symbol.getName().toString(),symbol));
												options.add(new SimpleLine(tempTokens));
											}
										}
										else if (memoryAllowed && symbol instanceof GlobalVariable) {
											GlobalVariable variable = (GlobalVariable)symbol;
											boolean allowed = parameterAllowed;
											boolean isStructure = false;
											if (!allowed) {
												DataType type = DataTypeUtil.getDataType(variable.getDefaultType(),variable.getLanguage().getScope(),scope,variable.getMode());
												boolean isCompatibleType = DataTypeUtil.isCompatibleType(DataTypeUtil.getOperandType(type),allowedTypes);
												// NOTE: Structures are allowed in case one of its members is the right type.
												allowed = type instanceof Structure || isCompatibleType;
												isStructure = type instanceof Structure && !isCompatibleType;
											}
											if (allowed) {
												ArrayList<Token> tempTokens = new ArrayList<Token>(2);
												tempTokens.add(new Token(symbol.getName().toString(),symbol));
												if (isStructure) {
													tempTokens.add(new Token(".",Line.OPERATOR_MARKER));
												}
												options.add(new SimpleLine(tempTokens));
											}
										}
										else if (memoryAllowed && symbol instanceof LocalVariable) {
											LocalVariable variable = (LocalVariable)symbol;
											boolean allowed = parameterAllowed;
											if (!allowed) {
												DataType type = DataTypeUtil.getDataType(variable.getType().toString(),languageScope,scope,currentMode);
												// NOTE: Structures are allowed in case one of its members is the right type.
												allowed = type instanceof Structure || DataTypeUtil.isCompatibleType(DataTypeUtil.getOperandType(type),allowedTypes);
											}
											if (allowed) {
												ArrayList<Token> tempTokens = new ArrayList<Token>(1);
												tempTokens.add(new Token(symbol.getName().toString(),symbol));
												options.add(new SimpleLine(tempTokens));
											}
										}
										else if (functionAllowed && symbol instanceof Function) {
											ArrayList<Token> tempTokens = new ArrayList<Token>(1);
											tempTokens.add(new Token(symbol.getName().toString(),symbol));
											options.add(new SimpleLine(tempTokens));
										}
										else if (labelAllowed && symbol instanceof LineLabel) {
											ArrayList<Token> tempTokens = new ArrayList<Token>(1);
											tempTokens.add(new Token(symbol.getName().toString(),symbol));
											options.add(new SimpleLine(tempTokens));
										}
									}
								}
							}
						}
						// Beginning of an operand after typecast
						else if (refs[prevToken]==ASM.PTR || (curToken>=0 && refs[curToken]==ASM.PTR && alreadyTyped.length()==curTokenLength)) {
							if (curToken>=0 && refs[curToken]==ASM.PTR) {
								event = replaceSelection(" ");
								++column;
								alreadyTyped = "";
							}
							for (Symbol symbol : scope.getAll()) {
								if (symbol instanceof LocalVariable || symbol instanceof GlobalVariable) {
									ArrayList<Token> tempTokens = new ArrayList<Token>(1);
									tempTokens.add(new Token(symbol.getName().toString(),symbol));
									options.add(new SimpleLine(tempTokens));
								}
							}
						}
						// Immediately following "."
						else if (line.isOperator(prevToken,'.')) {
							DataType currentType = null;
							// Don't need to include Structures if the "." is immediately preceded by a Structure
							boolean noNeedStructures = (refs[prevToken-1] instanceof Structure);
							boolean insideBrackets = false;
							for (int i = prevToken-1;i>=0;--i) {
								Object obj = refs[i];
								if (obj!=null) {
									if (!insideBrackets && obj instanceof StructureMember) {
										currentType = DataTypeUtil.getDataType(((StructureMember)obj).getType().toString(),(currentType!=null && currentType instanceof Structure) ? ((Structure)currentType).getLanguage().getScope() : languageScope,scope,(currentType!=null && currentType instanceof Structure) ? ((Structure)currentType).getMode() : currentMode);
										break;
									}
									else if (!insideBrackets && obj instanceof LocalVariable) {
										currentType = DataTypeUtil.getDataType(((LocalVariable)obj).getType().toString(),languageScope,scope,currentMode);
										break;
									}
									else if (!insideBrackets && obj instanceof GlobalVariable) {
										currentType = DataTypeUtil.getDataType(((GlobalVariable)obj).getDefaultType(),((GlobalVariable)obj).getLanguage().getScope(),scope,((GlobalVariable)obj).getMode());
										break;
									}
									else if (insideBrackets && obj instanceof Register) {
										currentType = line.getAssumptions().get(obj);
										if (currentType!=null) {
											break;
										}
									}
									else if (!insideBrackets && obj instanceof Structure) {
										currentType = (Structure)obj;
										break;
									}
									else if (line.isOperator(i,',')) {
										break;
									}
									else if (line.isOperator(i,'[')) {
										insideBrackets = false;
									}
									else if (line.isOperator(i,']')) {
										insideBrackets = true;
									}
								}
							}
							if (currentType!=null && currentType instanceof Structure) {
								ArrayList<StructureMember> members = ((Structure)currentType).getMembers();
								for (StructureMember member : members) {
									ArrayList<Token> tempTokens = new ArrayList<Token>(1);
									tempTokens.add(new Token(member.getName().toString(),member));
									options.add(new SimpleLine(tempTokens));
								}
							}
							// FIXME: Have intelligent and unintelligent auto-complete so that people can have Structures included if they really want.
	//						if (!noNeedStructures) {
	//							for (Symbol symbol : scope.getAll()) {
	//								if (symbol instanceof Structure && symbol!=currentType) {
	//									ArrayList<Token> tempTokens = new ArrayList<Token>(1);
	//									tempTokens.add(new Token(symbol.getName().toString(),symbol));
	//									options.add(new SimpleLine(tempTokens));
	//								}
	//							}
	//						}
						}
						// Inside [ ]
						else if (isInsideSquareBrackets) {
							if (refs[prevToken] instanceof Register || (curToken>=0 && refs[curToken] instanceof Register && alreadyTyped.length()==curTokenLength)) {
								if (curToken>=0 && refs[curToken] instanceof Register && alreadyTyped.length()==curTokenLength) {
									event = replaceSelection("+");
									++column;
									alreadyTyped = "";
								}
								for (Symbol symbol : languageScope.getAll()) {
									if (symbol instanceof Register && DataTypeUtil.isAddressReg((Register)symbol,currentMode)) {
										ArrayList<Token> tempTokens = new ArrayList<Token>(1);
										tempTokens.add(new Token(symbol.getName().toString(),symbol));
										options.add(new SimpleLine(tempTokens));
									}
									else if (symbol instanceof Directive.UnaryDirective) {
										ArrayList<Token> tempTokens = new ArrayList<Token>(2);
										tempTokens.add(new Token(symbol.getName().toString(),symbol));
										tempTokens.add(new Token(" ",Line.WHITE_SPACE_MARKER));
										options.add(new SimpleLine(tempTokens));
									}
								}
								for (Symbol symbol : scope.getAll()) {
									if (symbol instanceof Constant) {
										ArrayList<Token> tempTokens = new ArrayList<Token>(1);
										tempTokens.add(new Token(symbol.getName().toString(),symbol));
										options.add(new SimpleLine(tempTokens));
									}
								}
							}
							else if (line.isOperator(prevToken,'[') || (line.isOperator(prevToken,'+') && refs[prevPrevToken] instanceof Register)) {
								for (Symbol symbol : languageScope.getAll()) {
									if (symbol instanceof Register && DataTypeUtil.isAddressReg((Register)symbol,currentMode)) {
										ArrayList<Token> tempTokens = new ArrayList<Token>(1);
										tempTokens.add(new Token(symbol.getName().toString(),symbol));
										options.add(new SimpleLine(tempTokens));
									}
									else if (symbol instanceof Directive.UnaryDirective) {
										ArrayList<Token> tempTokens = new ArrayList<Token>(2);
										tempTokens.add(new Token(symbol.getName().toString(),symbol));
										tempTokens.add(new Token(" ",Line.WHITE_SPACE_MARKER));
										options.add(new SimpleLine(tempTokens));
									}
								}
								for (Symbol symbol : scope.getAll()) {
									if (symbol instanceof Constant) {
										ArrayList<Token> tempTokens = new ArrayList<Token>(1);
										tempTokens.add(new Token(symbol.getName().toString(),symbol));
										options.add(new SimpleLine(tempTokens));
									}
								}
							}
							else if (line.isOperator(prevToken,'*') && refs[prevPrevToken] instanceof Register) {
								ArrayList<Token> tempTokens = new ArrayList<Token>(1);
								tempTokens.add(new Token("2",new Long(2)));
								options.add(new SimpleLine(tempTokens));
								tempTokens = new ArrayList<Token>(1);
								tempTokens.add(new Token("4",new Long(4)));
								options.add(new SimpleLine(tempTokens));
								tempTokens = new ArrayList<Token>(1);
								tempTokens.add(new Token("8",new Long(8)));
								options.add(new SimpleLine(tempTokens));
								for (Symbol symbol : languageScope.getAll()) {
									if (symbol instanceof Directive.UnaryDirective) {
										tempTokens = new ArrayList<Token>(2);
										tempTokens.add(new Token(symbol.getName().toString(),symbol));
										tempTokens.add(new Token(" ",Line.WHITE_SPACE_MARKER));
										options.add(new SimpleLine(tempTokens));
									}
								}
								for (Symbol symbol : scope.getAll()) {
									if (symbol instanceof Constant) {
										Immediate n = ((Constant)symbol).getImmediate();
										if (n!=null && n.relocation==null && n.diffRelocation==null && (n.constant==1 || n.constant==2 || n.constant==4 || n.constant==8)) {
											tempTokens = new ArrayList<Token>(1);
											tempTokens.add(new Token(symbol.getName().toString(),symbol));
											options.add(new SimpleLine(tempTokens));
										}
									}
								}
							}
							else if (refs[prevToken]==Line.OPERATOR_MARKER || refs[prevToken] instanceof Directive.BinaryDirective) {
								for (Symbol symbol : languageScope.getAll()) {
									if (symbol instanceof Directive.UnaryDirective) {
										ArrayList<Token> tempTokens = new ArrayList<Token>(2);
										tempTokens.add(new Token(symbol.getName().toString(),symbol));
										tempTokens.add(new Token(" ",Line.WHITE_SPACE_MARKER));
										options.add(new SimpleLine(tempTokens));
									}
								}
								for (Symbol symbol : scope.getAll()) {
									if (symbol instanceof Constant) {
										ArrayList<Token> tempTokens = new ArrayList<Token>(1);
										tempTokens.add(new Token(symbol.getName().toString(),symbol));
										options.add(new SimpleLine(tempTokens));
									}
								}
							}
						}
//						else {
//							ErrorHandler.showNotImplementedMessage("Sorry, I'm not exactly sure what should go here, so auto-complete isn't implemented for it yet.");
//						}

						if (!options.isEmpty()) {
							// Remove empty strings; mostly an issue with anonymous structure members, but also anonymous variables
							for (int i=0;i<options.size();++i) {
								if (options.get(i).length()==0) {
									if (i<options.size()-1) {
										options.set(i,options.remove(options.size()-1));
									}
									else {
										options.remove(i);
									}
									--i;
								}
							}
							// Sort the options into alphabetical order.
							Collections.sort(options);
							AutoCompleteList autocompleteList = new AutoCompleteList(PwnIDE.editor, options,new Image[options.size()],new float[options.size()],alreadyTyped);
							if (autocompleteList.getContentShown().size()>1) {
								// Fill in as much as is in common between all shown autocomplete options
								String text = autocompleteList.fillIn();
								Point range = getAutcompleteColumnRange(lineNum,column,false);
								int startCol = range.x;
								int endCol = range.y;
								TextReplacementEvent anEvent = lines.replace(lineNum,startCol,lineNum,endCol,text);
								event = (event!=null) ? new UndoSet(new Undoable[]{event,anEvent},anEvent.getDescription()) : anEvent;
								String[] split = StringUtil.getLines(text);
								lineNum += split.length-1;
								if (split.length==1) {
									startCol += split[0].length();
								}
								else {
									startCol = split[split.length-1].length();
								}
								caret.set(lineNum,startCol,LineRenderer.getX(lines.get(lineNum),startCol));
								this.autocompleteList = autocompleteList;
								autocompleteList.setPopupLocation(getAutocompleteLocation(lineNum,startCol));
								autocompleteList.setVisible(true);
							}
							else if (autocompleteList.getContentShown().size()==1) {
								String text = options.get(autocompleteList.getContentShown().get(0)).toString();
								Point range = getAutcompleteColumnRange(lineNum,column,true);
								int startCol = range.x;
								int endCol = range.y;
								if (column==endCol) {
									TextReplacementEvent anEvent = lines.replace(lineNum,startCol,lineNum,endCol,text);
									event = (event!=null) ? new UndoSet(new Undoable[]{event,anEvent},anEvent.getDescription()) : anEvent;
									String[] split = StringUtil.getLines(text);
									lineNum += split.length-1;
									if (split.length==1) {
										startCol += split[0].length();
									}
									else {
										startCol = split[split.length-1].length();
									}
									caret.set(lineNum,startCol,LineRenderer.getX(lines.get(lineNum),startCol));
								}
								else {
									// Fill in as much as is in common between all shown autocomplete options
									text = autocompleteList.fillIn();
									range = getAutcompleteColumnRange(lineNum,column,false);
									startCol = range.x;
									endCol = range.y;
									TextReplacementEvent anEvent = lines.replace(lineNum,startCol,lineNum,endCol,text);
									event = (event!=null) ? new UndoSet(new Undoable[]{event,anEvent},anEvent.getDescription()) : anEvent;
									String[] split = StringUtil.getLines(text);
									lineNum += split.length-1;
									if (split.length==1) {
										startCol += split[0].length();
									}
									else {
										startCol = split[split.length-1].length();
									}
									caret.set(lineNum,startCol,LineRenderer.getX(lines.get(lineNum),startCol));
									this.autocompleteList = autocompleteList;
									autocompleteList.setPopupLocation(getAutocompleteLocation(lineNum,startCol));
									autocompleteList.setVisible(true);
								}
							}
						}
					}
					else { // autocompleteList!=null
						// Fill in as much as is in common between all shown autocomplete options
						String text = autocompleteList.fillIn();
						int lineNum = caret.getHeadLine();
						int column = caret.getHeadCol();
						Point range = getAutcompleteColumnRange(lineNum,column,false);
						int startCol = range.x;
						int endCol = range.y;
						event = lines.replace(lineNum,startCol,lineNum,endCol,text);
						String[] split = StringUtil.getLines(text);
						lineNum += split.length-1;
						if (split.length==1) {
							startCol += split[0].length();
						}
						else {
							startCol = split[split.length-1].length();
						}
						caret.set(lineNum,startCol,LineRenderer.getX(lines.get(lineNum),startCol));
						autocompleteList.setPopupLocation(getAutocompleteLocation(lineNum,column));
						killAutocomplete = false;
					}
				}
				else {
					ObservedInt undoCombiningRef = new ObservedInt(newUndoCombining);
					ObservedBoolean killAutocompleteRef = new ObservedBoolean(killAutocomplete);
					event = super.keyPressedCommon(e,undoCombiningRef,killAutocompleteRef);
					newUndoCombining = undoCombiningRef.get();
					killAutocomplete = killAutocompleteRef.get();
				}
				restartCaretBlink();
				repaint();
				if (event!=null) {
					PwnIDE.editor.eventDone(event);
				}
			}
			// NOTE: This must be outside the "if" so that the undoCombining is cleared when undo or redo is done
			undoCombining = newUndoCombining;
			if (killAutocomplete) {
				autocompleteList.setVisible(false);
				autocompleteList = null;
			}
		}
		catch (Throwable t) {
			ErrorHandler.showException(t,"Sorry, an error occurred while handling a key press.\nIt may or may not have worked, and Inventor IDE might not be stable anymore,\nso I'd recommend backing up the files in your project, saving, then restarting Inventor IDE.\nIf the files don't work, the backed up files should still work.");
		}
	}
}
